package client

import (
	"flag"
	"fmt"
	"os"
	"publicChainSystem/block"
	"publicChainSystem/tools"
	"publicChainSystem/transaction"
)

/**
 *@author:zhengyilong
 *@email:1797344574@qq.com
 *@phone:13479394730
 *@DateTime:2022/3/28 10:53
 **/

/**
用户的程序交互入口
只负责读取用户的命令和参数，并进行解析
解析后调用对应的功能
 */

type Cli struct {

}

func (cl *Cli)Run()  {
	//获取用户输入的参数
	//args := os.Args

	/*确定系统有哪些功能，需要哪些参数
	a.创建带有创世区块的区块链，系统给定一个地址用于存储创世区块奖励地址，参数：无
	main.exe createchain
	b.发起一笔交易，参数：有 3个
	main.exe send --from "zhangsan" --to "liu" --amout 50
	c.打印所有区块信息	，参数： 无
	main.exe printblock
	d.获取当前区块链中区块的个数，参数：无
	main.exe getblockcount
	e.查看指定用户的余额，参数：有1个 用户地址
	main.exe showUserAmout --address "li"
	f.创建比特币地址，参数：无
	main.exe createAddress
	g.校验地址是否合法，参数：1个 校验的地址
	main.exe checkAddress --address "1AK2AJvnaz7zypXB3mNkDBNckp19Dxqpe3"
	h.根据地址从桶中拿到私钥，参数：1个 地址
	main.exe showPrikey --address "1AK2AJvnaz7zypXB3mNkDBNckp19Dxqpe3"
	h.输出当前系统的使用说明，参数：无
	main.exe help
	*/

	switch os.Args[1] {
	case "createchain":
		cl.createchain()
	//case "addblock":
	//	cl.addblock()
	case "send":
		cl.send()
	case "printblock":
		cl.printblock()
	case "getblockcount":
		cl.getblockcount()
	case "showUserAmout":
		cl.showUserAmout()
	case "createAddress":
		cl.createAddress()
	case "checkAddress":
		cl.checkAddress()
	case "showPrikey":
		cl.showPrikey()
	case "help":
		cl.help()
	default:
		fmt.Println("暂不支持该功能")
	}

}

//包内方法，只可在本包内通过cl对象调用，配合Run()方法中的switch case使用。方便Run()方法简洁明了
func (cl *Cli)createchain() {
	////设置功能名并获取功能对象
	//createchain := flag.NewFlagSet("createchain", flag.ExitOnError)
	//
	////设置参数
	//addr := createchain.String("address", "", "账户名称")
	//createchain.Parse(os.Args[2:])

	//判断区块是否已存在
	exist := tools.FileExist("./chain.db")
	if exist {
		fmt.Println("已经存在")
		return
	}

	////判断地址是否合法
	//checkAddr := wallet.CheckAddr(*addr)
	//if !checkAddr {
	//	fmt.Println("地址不合法")
	//	return
	//}

	//创建区块
	//bc, err := block.NewChain(*addr)
	bc, err := block.NewChain("")
	defer bc.DB.Close()
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	fmt.Println("创建区块链成功")
}

//包内方法，只可在本包内通过cl对象调用，配合Run()方法中的switch case使用。方便Run()方法简洁明了
//func (cl *Cli)addblock() {
//	//设置功能名并获取功能对象
//	addblock := flag.NewFlagSet("addblock", flag.ExitOnError)
//
//	//设置参数
//	s := addblock.String("data", "", "添加区块的交易信息")
//	addblock.Parse(os.Args[2:])
//
//	//判断区块链是否存在
//	exist := tools.FileExist("./chain.db")
//	if !exist {
//		fmt.Println("无区块链")
//		return
//	}
//
//	//获取blockChain对象
//	chain, err := block.NewChain(nil)
//	defer chain.DB.Close()
//	if err != nil {
//		fmt.Println(err.Error())
//		return
//	}
//
//	//添加区块
//	err = chain.AddBlock([]byte(*s))
//	if err != nil {
//		fmt.Println(err.Error())
//		return
//	}
//
//	fmt.Println("添加成功")
//}

//发起一笔交易，把区块再添加到区块链里面
func (cl *Cli) send() {
	//判断区块链是否存在
	exist := tools.FileExist("./chain.db")
	if !exist {
		fmt.Println("无区块链")
		return
	}

	send := flag.NewFlagSet("send", flag.ExitOnError)
	from := send.String("from", "", "交易发起者的地址")
	to := send.String("to", "", "交易接受者的地址")
	//正整数Uint
	amout := send.Uint("amout",0,"交易的数量")


	//参数的解析
	err := send.Parse(os.Args[2:])
	if err != nil {
		fmt.Println("解析失败",err.Error())
		return
	}

	//1.创建一个普通的交易
	//tx,err := transaction.NewTransaction(*from, *to, *amout)
	//if err != nil {
	//	fmt.Println(err.Error())
	//	return
	//}

	//2.把这个交易放到区块中，然后再把区块存储到区块链中
	//在2这个过程中，产生新区块的过程中，会产生一个coinbase交易
	bc, err := block.NewChain("")
	defer bc.DB.Close()

	//判断地址是否合法
	checkfrom := bc.Wallet.Checkaddr(*from)
	if !checkfrom {
		fmt.Println("from地址不合法")
		return
	}
	checkto := bc.Wallet.Checkaddr(*to)
	if !checkto {
		fmt.Println("to地址不合法")
		return
	}

	if err != nil {
		fmt.Println(err.Error())
		return
	}
	//创建一个普通交易
	tx, err := bc.NewTransaction(*from, *to, *amout)
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	//创建一个coinbase交易
	cb, err := bc.NewCoinbase(*from)
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	err = bc.AddBlock([]transaction.Transaction{*tx, *cb})
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	fmt.Println("转账成功")

}

//包内方法，只可在本包内通过cl对象调用，配合Run()方法中的switch case使用。方便Run()方法简洁明了
func (cl *Cli)printblock() {
	//判断区块链是否存在
	exist := tools.FileExist("./chain.db")
	if !exist {
		fmt.Println("无区块链")
		return
	}

	//获取blockChain对象
	bc, err := block.NewChain("")
	defer bc.DB.Close()
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	//获取到区块链
	allBlock, err := bc.GetAllBlock()
	if err != nil {
		fmt.Println("获取失败：",err)
		return
	}
	//遍历获取每一个区块
	for _,block := range allBlock {
		fmt.Println("---------------------------------------------------------------------------------------")
		fmt.Printf("上一个区块哈希：%x\n",block.PrevHash)
		fmt.Printf("时间戳：%d\n",block.TimeStamp)
		fmt.Printf("区块哈希：%x\n",block.Hash)
		fmt.Printf("交易个数：%d\n", len(block.Txs))
		fmt.Printf("随机数：%d\n",block.Nonce)
		//遍历交易集合
		for i, tx := range block.Txs {
			fmt.Printf("\t第%d笔交易：",i)
			fmt.Printf("\t交易hash：%x\n",tx.Txid)

			fmt.Printf("\t\t有%d个交易输入\n",len(tx.Input))
			for index,input := range tx.Input {
				fmt.Printf("\t\t\t消费%d，来自%x，下标%d\n",index,input.TXid,input.Vout)
			}

			fmt.Printf("\t\t有%d个交易输出\n",len(tx.Output))
			for index,output := range tx.Output {
				fmt.Printf("\t\t\t收入下标%d,金额%d,属于%x\n",index,output.Value,output.ScriptPubkey)
			}
		}
		fmt.Println("---------------------------------------------------------------------------------------")
	}
}

//包内方法，只可在本包内通过cl对象调用，配合Run()方法中的switch case使用。方便Run()方法简洁明了
func (cl *Cli)getblockcount() {
	//判断区块链是否存在
	exist := tools.FileExist("./chain.db")
	if !exist {
		fmt.Println("无区块链")
		return
	}

	//获取blockChain对象
	bc, err := block.NewChain("")
	defer bc.DB.Close()
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	//获取个数
	allBlock, err := bc.GetAllBlock()
	if err != nil {
		fmt.Println("获取失败：",err)
		return
	}

	fmt.Printf("区块链含有%d个区块\n",len(allBlock))
}

//查看指定用户的余额
func (cl *Cli) showUserAmout() {
	//判断区块链是否存在
	exist := tools.FileExist("./chain.db")
	if !exist {
		fmt.Println("无区块链")
		return
	}

	//设置功能名并获取功能对象
	showUserAmout := flag.NewFlagSet("showUserAmout", flag.ExitOnError)

	//设置参数
	addr := showUserAmout.String("address", "", "账户名称")
	showUserAmout.Parse(os.Args[2:])

	bc, _ := block.NewChain("")
	defer bc.DB.Close()

	//判断地址是否合法
	checkAddr := bc.Wallet.Checkaddr(*addr)
	if !checkAddr {
		fmt.Println("地址不合法")
		return
	}

	amout := bc.ShowUserAmout(*addr)

	fmt.Println(*addr,"的余额为：",amout)
}

//创建地址
func (cl *Cli) createAddress() {

	bc, err := block.NewChain("")
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	address,pri, err := bc.Wallet.NewAddress()
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	err = bc.Wallet.SavePrivateKey(address, pri)
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	fmt.Println(address)
}

//校验地址
func (cl *Cli) checkAddress() {
	//设置功能名并获取功能对象
	checkAddress := flag.NewFlagSet("checkAddress", flag.ExitOnError)

	//设置参数
	addr := checkAddress.String("address", "", "账户名称")
	checkAddress.Parse(os.Args[2:])

	bc, err := block.NewChain("")
	defer bc.DB.Close()
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	//进行校验
	checkAddr := bc.Wallet.Checkaddr(*addr)

	if checkAddr {
		fmt.Println("地址合法")
	}else {
		fmt.Println("地址不合法")
	}
}

//根据地址从桶中得到私钥
func (cl *Cli) showPrikey() {
	showPrikey := flag.NewFlagSet("showPrikey", flag.ExitOnError)
	addr := showPrikey.String("address", "", "要查询的参数")
	showPrikey.Parse(os.Args[2:])

	bc, err := block.NewChain("")
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	privateKey, err := bc.Wallet.ShowPrivateKey(*addr)
	if err!=nil {
		fmt.Println(err.Error())
		return
	}

	fmt.Println("私钥为：",privateKey)
}

//包内方法，只可在本包内通过cl对象调用，配合Run()方法中的switch case使用。方便Run()方法简洁明了
func (cl *Cli)help() {
	fmt.Println("\ta.创建带有创世区块的区块链，系统给定一个地址用于存储创世区块奖励地址，参数：无\n\tmain.exe createchain\n\t" +
		"b.发起一笔交易，参数：有 3个\n\tmain.exe send --from \"zhangsan\" --to \"liu\" --amout 50\n\t" +
		"c.打印所有区块信息\t，参数： 无\n\tmain.exe printblock\n\t" +
		"d.获取当前区块链中区块的个数，参数：无\n\tmain.exe getblockcount\n\t" +
		"e.查看指定用户的余额，参数：有1个 用户地址\n\tmain.exe showUserAmout --address \"li\"\n\t" +
		"f.创建比特币地址，参数：无\n\tmain.exe createAddress\n\t" +
		"g.校验地址是否合法，参数：1个 校验的地址\n\tmain.exe checkAddress --address \"1AK2AJvnaz7zypXB3mNkDBNckp19Dxqpe3\"\n\t" +
		"h.输出当前系统的使用说明，参数：无\n\tmain.exe help")
}